extends Camera2D

@export var dynamic_enabled: bool = false
@export_range(0, 1, 0.01) var dynamic_factor: float = 0.5

var _duration = 0.0
var _period_in_ms = 0.0
var _amplitude = 0.0
var _timer = 0.0
var _last_shook_timer = 0
var _previous_x = 0.0
var _previous_y = 0.0
var _last_offset = Vector2(0, 0)

@export var target:Node2D
var objects = null
#var max_dist = 2000.0
@onready var max_dist: float = (get_viewport_rect().size / 2.0).distance_to(get_viewport_rect().size)

@onready var normal_zoom = zoom
@onready var normal_position = position
@onready var normal_smoothing_speed = position_smoothing_speed

func _ready():
	Global.camera = self
	set_process(true)

# Shake with decreasing intensity while there's time remaining.
func _process(delta):
	if target:
		follow_target(delta)
	elif dynamic_enabled and objects != null:
		# Check if all objects are still in tree
		var objects_are_in_tree: bool = true
		for x in objects:
			if x != null && not x.is_inside_tree():
				objects_are_in_tree = false
				break
		
		if objects_are_in_tree and objects.size() > 0:
			var min_pos := Vector2(INF, INF)
			var max_pos := Vector2(-INF, -INF)
			for x in objects:
				if x == null:
					continue
				var p = x.global_position
				min_pos.x = min(min_pos.x, p.x)
				min_pos.y = min(min_pos.y, p.y)
				max_pos.x = max(max_pos.x, p.x)
				max_pos.y = max(max_pos.y, p.y)
			# Bounding box center and size
			var bbox_size = max_pos - min_pos
			var bbox_center = min_pos + bbox_size * 0.5
			# Position camera to center of bbox (simple follow)
			global_position = bbox_center
			# Compute zoom to fit all objects into view with small padding
			var viewport_size = get_viewport_rect().size
			var pad := 80.0
			var fit_x = viewport_size.x / max(bbox_size.x + pad, 1.0)
			var fit_y = viewport_size.y / max(bbox_size.y + pad, 1.0)
			var desired = min(fit_x, fit_y)
			var clamped = clamp(desired, 0.4, 1.2)
			zoom = Vector2(clamped, clamped)
	else:
		pass
	
	# Only shake when there's shake time remaining.
	if _timer == 0:
		return
	# Only shake on certain frames.
	_last_shook_timer = _last_shook_timer + delta
	# Be mathematically correct in the face of lag; usually only happens once.
	while _last_shook_timer >= _period_in_ms:
		_last_shook_timer = _last_shook_timer - _period_in_ms
		# Lerp between [amplitude] and 0.0 intensity based on remaining shake time.
		var intensity = _amplitude * (1 - ((_duration - _timer) / _duration))
		# Noise calculation logic from http://jonny.morrill.me/blog/view/14
		var new_x = randf_range(-1.0, 1.0)
		var x_component = intensity * (_previous_x + (delta * (new_x - _previous_x)))
		var new_y = randf_range(-1.0, 1.0)
		var y_component = intensity * (_previous_y + (delta * (new_y - _previous_y)))
		_previous_x = new_x
		_previous_y = new_y
		# Track how much we've moved the offset, as opposed to other effects.
		var new_offset = Vector2(x_component, y_component)
		set_offset(get_offset() - _last_offset + new_offset)
		_last_offset = new_offset
	# Reset the offset when we're done shaking.
	_timer = _timer - delta
	if _timer <= 0:
		_timer = 0
		set_offset(get_offset() - _last_offset)
		
func follow_target(delta: float) -> void:
	if top_level == false:
		global_position = lerp(global_position, target.global_position, 12.0*delta)

func reset_camera() -> void:
	global_position = normal_position
	zoom = normal_zoom

func start_tracking(new_target) -> void:
	position_smoothing_enabled = false
	target = new_target
	$Tween.remove_all()
	$Tween.interpolate_property(self, "zoom", zoom, Vector2(0.8, 0.8), 0.8, 
		Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
	$Tween.start()
	$Timer.start(1.5)
	
func stop_tracking() -> void:
	target = null
	position_smoothing_enabled = true
	$Tween.remove_all()
	$Tween.interpolate_property(self, "zoom", zoom, normal_zoom, 1.0, 
		Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
	$Tween.interpolate_property(self, "position", position, normal_position, 0.8, 
		Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
	$Tween.start()

# Kick off a new screenshake effect.
func shake(duration, frequency, amplitude):
	if frequency == 0: return
	# Initialize variables.
	_duration = duration
	_timer = duration
	_period_in_ms = 1.0 / frequency
	_amplitude = amplitude
	_previous_x = randf_range(-1.0, 1.0)
	_previous_y = randf_range(-1.0, 1.0)
	# Reset previous offset, if any.
	set_offset(get_offset() - _last_offset)
	_last_offset = Vector2(0, 0)

func _on_Timer_timeout() -> void:
	if target == null or not is_instance_valid(target): return
	$Tween.remove_all()
	$Tween.interpolate_property(self, "zoom", zoom, Vector2(1, 1), 1.5, 
		Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)
	$Tween.start()
